Teknisk Tirsdag: Unsupervised Learning

Tillykke du er i anden uge af din ansættelse i den norske virksomhed, som operere med data science. Din leder var meget tilfreds med analysen fra sidste uge. Vedkommende mener at vi skal begynde at kigge lidt bredere på spillere.

Vores kunder er interesseret i at undersøge hvordan forholdet mellem spillernes placeringer på banen er i forhold til deres fysiske egenskaber. I og med det gik så godt i sidste uge har vi fået ansvaret for at lave en analyse der viser dette.

Opgaver

  1. Diskutere hvorfor vi kan bruge clustering til at gruppere spillere.
  2. Isolere de kolonner som vi ønsker at basere analysen på.
  3. Første kørsel med KMeans, leg med antal af cluster
  4. Kør den forbedret version af problemet og med hyperparameterne optimering.
  5. Noget med at fortolke hvilke spillere der er outliers i deres spiller-gruppe... Kig på outliers, og brug forskellige outlier metrikker.

In [ ]:
#PURE PYTHON!!!!
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sb
from sklearn import cluster

Import af sidste uges notebook til dagens øvelse

Der er ingen grund til at lave dobbeltarbejde.


In [ ]:
# Run the datacleaning notebook to get all the variables
%run 'Teknisk Tirsdag - Data Cleaning.ipynb'

Opgave 0: Hvorfor giver det mening at bruge clustering?

Diskutere med andre, i 5 min, om hvorfor det kan være en god idé, at bruge clustering til at vise spillerpositioner. Hvilken viden kan vi få ud af det? Kan der være problemer med denne analyse og hvorfor? Kom gerne med eksempler på om vi kan overføre denne tankegang til det virkelige verden.

Vi tager en kort gennemgang af denne opgave, inden vi forsætter med resten.

Opgave 1: Find vores feature kolonner.

Som data scientist er det uundgåeligt, ikke at skulle få beskidte fingre og arbejde med noget kode. I denne øvelse vil vi bruge nogle af Python's funktioner til at finde hhv. det indeks hvor de fysiske attributter starter og slutter. Bare rolig: Hvis alt andet fejler, kan vi også tælle os frem til løsningen :-)

Lidt om Pandas dataframes

Et pandas dataframe har en attribut som indeholder en liste af kolonner. Man tilgår attributten som på mange andre kodesprog ved at bruge punktum efter objektes navn fx. x.attribut.

  1. Først skal vi indeksere elementerne i den udtrukket liste. Der er mange veje til målet, dette er nok den nemmeste

  2. Vores næste opgave er at finde de fysiske egenskaber; heldigvis ligger alle de fysiske egenskaber i rækkefølge, så vi har kun brug for den første og den sidste, da vi derefter kan udtrække dem alle.

  3. Nu skal resultatet fra den første og anden opgave kombineres. Udtræk indekset for hhv. start og slut kolonnen.

Tip: Som i mange Objekt Orienteret Sporg findes der mange veje til målet. Dine bedste redskaber til at komme til målet er: Din sunde fornuft og Google...

Hvert element i vores liste tager formen: (indeks, kolonne_med_første_fysisk_egenskab), hvis vi kalder et element i listen for x, kan indeks f.eks. tilgås ved x[0]. Et godt sted at starte er at bruge filter filter metoden.


In [ ]:
# Vi bruger alle spillere i FIFAs katelog.
attribute_df = df.copy()
position_df = attribute_df[['Name','Preferred Positions', 'Age', 'Nationality']]

In [ ]:
attribute_df = attribute_df.set_index(['Name']);
position_df = position_df.set_index('Name');

In [ ]:
attribute_df.head()

In [ ]:
position_df.head()

In [ ]:
enumareted_cols =  None ### INDSÆT KODE HER ###

# For vist liste, fjern havelågen:

# enumareted_cols

In [ ]:
attribute_start_col = None ### INDSÆT FØRSTE FYSISKE EGENSKAB ###
print(attribute_start_col)

In [ ]:
attribute_end_col = None ### INDSÆT FØRSTE FYSISKE EGENSKAB ###
print(attribute_end_col)

In [ ]:
# Vi ordner resten for dig.
attribute_col = list(range(attribute_start_col, attribute_end_col+1, 1))

In [ ]:
attribute_df = attribute_df.iloc[:, attribute_col]

Bekræft at vi har kolonnerne med


In [ ]:
print(list(attribute_df.columns))

In [ ]:
attribute_df.head()

Indledende statistik

For bedre at kunne forstå vores datasæt, giver det god menning at danne sig et overblik over data. Dette gøres ved describe() metoden, som laver opsummerende statistik på alle numeriske kolonner.


In [ ]:
attribute_df.describe()

Opgave 1 forsat:

Brug 5 minutter på at diskutere hvad denne statistik betyder. Kan vi få noget vigtigt ud af denne tabel?

Fortrukne positioner

I vores datasæt findes der også fortrukne positioner. Dem har vi trukket ud til postition_df, som vises under denne celle.


In [ ]:
position_df.loc[:,'position_list'] = np.array(position_df['Preferred Positions'].str.split(' ').tolist())

In [ ]:
position_df.head()

Opgave 2: Første kørsel med KMeans

Da alt databearbejdningen fandt sted i sidste uge, er der blot tilbage, at kører den egentlig analyse. Til dette har vi KMeans. Denne algoritme bruger et afstandsmål som reference til at skabe grupperinger. Ud fra afstandsmålne kan vi skabe nogle cluster centre, centriods.

  • Din første opgave er at køre KMeans. Vælg et passende antal cluster, grupper, du synes kunne være relevant, som data skal deles i - man må gerne diskutere med andre om valg af antal clustre.

Inden vi kommer så vidt, er vi nødt til at omforme positionerne således de kan sammenlignes med det resultat vi får fra vores Kmeans-algoritme.


In [ ]:
from sklearn.preprocessing import MultiLabelBinarizer
from scipy.spatial.distance import cdist
mlb = MultiLabelBinarizer()
labels = mlb.fit(position_df['position_list'])

In [ ]:
real_position_list = [i for i in labels.classes_ if i != '']
position_df[labels.classes_] = pd.DataFrame(labels.transform(position_df['position_list']), index=position_df.index, columns=labels.classes_)
del position_df['']
del position_df['position_list']
position_df.head()

Kør selve KMeans


In [ ]:
def run_kmeans(attributes, lables, k = 20):
    kmeans = cluster.KMeans(n_clusters= k, random_state= True)
    model = kmeans.fit(attributes)
    lables['prediction'] = model.predict(attributes)
    
    # Vi indsætter centerpunkterne i en midlertidig dataframe Z
    Z = pd.DataFrame(model.cluster_centers_[lables['prediction']], columns=attributes.columns)
    
    #Beregner afstand
    lables['distance'] = np.linalg.norm(attributes.as_matrix()-Z.as_matrix(), axis=1)
    return lables

n_clusters = None ### INDSÆT ANTAL CLUSTRE ###
position_df = run_kmeans(attribute_df, position_df, n_clusters)

In [ ]:
position_df.head()

In [ ]:
def print_cluster_til_position(df, columns, **kwargs):
    
    size = kwargs.get('figsize',(20, 10))
    
    grouped_by_position_df = df.groupby('prediction', as_index=True)[columns].sum()
    f, ax = plt.subplots(1,1,figsize=size)
    sb.set(style="ticks")
    sb.heatmap(grouped_by_position_df, annot=True, fmt="d", linewidths=1., ax=ax)
    ax.set_xlabel('Spillerpositioner', fontsize= 20)
    ax.set_ylabel('Cluster', fontsize= 12, rotation='horizontal')
    plt.show()

In [ ]:
print_cluster_til_position(position_df,real_position_list)

In [ ]:
fig = plt.figure(figsize=(20, 10))
ax = fig.gca()
sb.violinplot(x='prediction', y='distance', data=position_df, orient='v', ax=ax)
ax.set_xlabel('Cluster', fontsize= 18)
ax.set_ylabel('Afstand', rotation='horizontal', fontsize= 20)
plt.show()

Den øverste figur viser fordelingen mellem hvad KMeans grupperer spillerne som (predictions opad y-aksen), i forhold til deres fortrukne spillerpositioner (ud af x-aksen). Den nederste figur viser fordelingen af afstande for hvert cluster. Der hvor hver "violin" buler ud viser hvor den største koncentration af punkter ligger.

Her er et billedet over positionerne med deres respektive forkortelser. Dette skulle gerne hjælpe i tabellen ovenfor.

Diskutere hvad tallene i tabellen betyder. Giver dette resultat mening?

Opgave 3:

Første forsøg med at køre KMeans var ikke så sucessfuldt som håbet. Nogle af de fejlkilder som er i eksemplet er at flere spillere har mange fortrukne positioner; mange af disse positioner er tæt på hinanden f.eks. LWB og LB.

Generelt ses det, at vi har variationer i positioner med 3-bogstavskombinationer, dette kunne vi rette op på. En anden fejlkilde er at vi tager rådata ind. Dette kan ødelægge vores analyse, ved f.eks. at indfører gigantiske afstande i visse clustre. Normalt fortages der en standardisering af ens data.


In [ ]:
from sklearn.preprocessing import scale

3.1 Omdan positionskoder

Denne opgave er lidt banal, men brug den logiske operator som gør at vi kan sætte to kolonner sammen. Du har følgende valgmuligheder: and: & og or: |


In [ ]:
simplied_position_df =  position_df.copy()
del simplied_position_df['prediction']
# Samler CAM og CDM til CM
simplied_position_df['CM'] = (simplied_position_df['CAM']  &# INDSÆT BINÆR OPERATOR
                              simplied_position_df['CM']  &# INDSÆT BINÆR OPERATOR
                              simplied_position_df['CDM']
                             )
del simplied_position_df['CAM']
del simplied_position_df['CDM']

simplied_position_df['RB'] = (simplied_position_df['RB']  &# INDSÆT BINÆR OPERATOR
                              simplied_position_df['RWB'])
simplied_position_df['LB'] = (simplied_position_df['LB']  &# INDSÆT BINÆR OPERATOR
                              simplied_position_df['LWB'])
del simplied_position_df['RWB']
del simplied_position_df['LWB']

simplied_position_df.head()

Det ses nu at vi har kun 2-bogstavskoder, og derved har vi simplificeret vores problem meget.

Skalering af data

Det næste vi kan gøre er at skalere vores træningssæt for at uligne store afstande i mellem datapunkter.


In [ ]:
attribute_df.head()

In [ ]:
scaled_attributes_df = pd.DataFrame(
    scale(attribute_df),
    columns=attribute_df.columns,
    index=attribute_df.index
)
scaled_attributes_df.head()

For at teste om gennemsnittet er 0 og standardafvigelsen er 1.0 kan vi igen bruge describe()


In [ ]:
scaled_attributes_df.describe()

Det ser jo fornuftigt ud

Opgave 3.2: Hyperparameter optimering - Elbow method

Den forrige analyse var ikke særlig tilfredsstillende - Vi havde ingen idé hvilket antal clusters vi skulle bruge.

Som data scientist har vi nogle værktøjer, som vi kan bruge til at træffe en beslutning på et mere oplysende grundlag.
Den mest intuitive metode er at undersøge hvor god Kmeans er til at cluster data. KMeans outputter, foruden grupperingerne, også et mål for hvor god modellen er. Kmeans basere dette mål på kvadreret afstand fra alle datapunkter til deres respektive cluster-centrum. Dvs. detso lavere en værdi, desto tættere ligger datapunkterne på centrum, og derved er modellen mere forklarende.

Ved at køre flere KMeans, med forskellig antal clusters kan man få et billedet af hvor godt modellen udvikler sig. Det man som data scientist gør, er at kigge efter et knæk i grafen, eller den såkaldte albue, hvor en øgelse af antal cluster ikke påvirker målet i så høj grad.

Undersøg nedenstående graf, og diskutere hvilket antal af cluster I ville bruge i den nye analyse. Giver det mere mening nu?


In [ ]:
iterations = 60
r = range(5, iterations+5, 5)
km = [cluster.KMeans(n_clusters=i) for i in r]
score = [i.fit(scaled_attributes_df).score(scaled_attributes_df) for i in km]

In [ ]:
fig = plt.figure(figsize=(20, 10))
ax = fig.gca()
ax.grid(True, axis='x')
plt.plot(np.array(r), -1*np.array(score), 'ro')
plt.xticks(range(5, iterations+5, 5))
plt.xlabel('Antal clustre', fontsize= 20)
plt.ylabel('z', rotation= 'horizontal', fontsize= 20)
plt.title('Værdien af hvor godt hver Kmeans klarer sig')
plt.show()

Det er tydeligt at se, at Elbow-metoden bare vil gå efter en lavere z-score. Dette er også menningen, men håbet var at vi i denne analyse ville se nogle plateau'er, gerne i den lave ende af antal clustre.

På trods af analysen mangler, skal der vælges et antal clustre.


In [ ]:
n_clusters = None ### INDSÆT ANTAL CLUSTRE ###
simplied_position_df = run_kmeans(scaled_attributes_df, simplied_position_df, n_clusters)
simplied_position_df.head()

In [ ]:
positions_list = "CB CF CM GK LB LM LW RB RM RW ST".split(' ')
print_cluster_til_position(simplied_position_df, positions_list)

Giver dit nye resultat mere mening? Hvordan fordeler de sig?

Entropi

Som en lidt mere advanceret metode til at validere om ens model gør det godt, kan vi bruge entropi.

Entropi stammer fra fysik og beskriver mængden af uorden i Universet. Når entropien er 0 vil Universet være perfekt ordnet, omvendt når vi har total kaos vil entropien være meget høj. Entropi bruges også i andre områder, heriblandt Machine Leraning.

Vi kan bruge dette begreb i vores cluster analyse. Antag at hvert cluster er et 'univers', og det perfekte univers indeholder kun elementer som er ens, dvs. én spillertype pr. cluster. Mere realistisk, men stadig ideelt, vil vores cluster bestå af en hoved spillertype, med nogle få outliers, i form af andre spillertype. Det vi ønsker er at finde det antal cluster som giver den laveste entropi.
Kør nedenstående celler. Giver analysen med entropi det samme som med elbow-metoden, diskutere?


In [ ]:
def get_best_model(attributes, lables, max_k = 60):
    positions_list = "CB CF CM GK LB LM LW RB RM RW ST".split(' ')
    all_players = lables[positions_list].sum().sum()
    resultater = []
    
    def entropi(pdf):
        n = pdf.sum(axis=1) # rækkevis summering
        return pdf.apply(lambda x: -x/n*np.log(x/n)).sum(axis=1, skipna=True)
    
    for i in range(5, max_k, 5):
        km = cluster.KMeans(n_clusters=i, random_state= True)
        lables['prediction'] = km.fit_predict(attributes)
        grouped_df = lables.groupby(
            'prediction', as_index=True)[positions_list].sum()
        
        weighted_sum_of_squared_entropi = (grouped_df.sum(axis=1)*entropi(grouped_df)).sum()/all_players
        print('Antal cluster: {}, entropi: {}'.format(i, weighted_sum_of_squared_entropi))
        resultater.append((i,weighted_sum_of_squared_entropi))
    return resultater

In [ ]:
entropi = get_best_model(scaled_attributes_df,simplied_position_df)

In [ ]:
fig = plt.figure(figsize=(20,10))
ax = fig.gca()
ax.grid(True, axis='x')
plt.plot(list(map(lambda x: x[0], entropi)), list(map(lambda x: x[1], entropi)),'ro')
plt.xticks(range(5, iterations+5, 5))
plt.xlabel('Antal clustre', fontsize= 20)
plt.ylabel('H', fontsize= 20, rotation= 'horizontal')
ax.set_xticklabels(ax.get_xticks(), fontsize= 15)
ax.set_yticklabels(ax.get_yticks(), fontsize= 15)
plt.title('Entropien som funktion af antal clustre')
plt.show()

Vi kører modellen igen igen


In [ ]:
n_clusters = None ### INDSÆT ANTAL CLUSTRE ###
entropi_position_df = run_kmeans(scaled_attributes_df, simplied_position_df, n_clusters)

print_cluster_til_position(entropi_position_df, positions_list, figsize=(20,15))

Sidste opgave: Outliers i clustre

Som en sidste analyse kan vi udtage et eller to clustre og se hvordan afstanden, fra datapunkt til centrum, forholder sig i clusteret.

Udvælg et cluster, som har en høj entropi, dvs. der er mange forskellige spillertyper i clusteret, og undersøg hvordan afstanden forholder sig. Brug nedenstående celle til at visualiser afstanden


In [ ]:
n_cluster = None ###INDSÆT ET CLUSTERTAL HER ###
entropi_position_df[entropi_position_df['prediction'] == n_cluster]['distance']
fig = plt.figure(figsize=(20, 10))
ax = fig.gca()
sb.distplot(entropi_position_df[entropi_position_df['prediction'] == n_cluster]['distance'], kde=False, ax=ax)
ax.set_xlabel('Afstand', fontsize= 20)
ax.set_xticklabels(ax.get_xticks(), fontsize= 15)
ax.set_yticklabels(ax.get_yticks(), fontsize= 15)
ax.set_ylabel('Antal', rotation='horizontal', fontsize= 20)
plt.show()

Udtræk den eller de spillere som I mener, baseret på histogrammet, skulle være en outlier. Hvem er det? Kan vi se hvad det er som gør dem til outliers?


In [ ]:
distance_boundary = None ### INDSÆT ET AFSTANDSMÅL HER ###

outliers = entropi_position_df[(entropi_position_df['prediction'] == n_cluster) &
                               (entropi_position_df['distance'] > distance_boundary)]

display(attribute_df[attribute_df.index.isin(outliers.index)])
display(outliers)

Tillykke du har nu gennemført Teknisk Tirsdag omkring Machine Learning.

I løbet af disse to gange har vi gennemgået

  • Motivationen bag machine learning
  • Arbejdet med forskellige data cleaning methoder, herunder: konvertering af tekst til tal og fjernelse af unyttige kolonner.
  • Supervised Learning
  • Unsupervised Learning

In [ ]: